--- title: "02-C++ 基础" created: 2025-11-25 tags: - 项目筑基 --- # C++ 基础 > 从 hello world 到通讯录管理系统:变量、数据类型、运算符、流程结构、数组、函数、指针、结构,一路把 C 语言底子搬到 C++。 ### hello C++ ```cpp #include using namespace std; int main() { cout << "hello C++" << endl; system("pause"); return 0; } ``` C++ 中以流的形式进行输入输出,而不再是 printf 和 scanf;头文件也要修改成 iostream,不需要 .h。 那 `using namespace std;` 是什么含义??? endl 可以省略,作用为换行。 ### 变量 常量 命名 #### 变量 作用:给一段指定的内存空间起名,方便操作这段内存。 语法:`数据类型 变量名 = 初始值;` ```cpp #include using namespace std; int main() { // 变量的定义 // 语法:数据类型 变量名 = 初始值 int a = 10; cout << "a = " << a << endl; system("pause"); return 0; } ``` 注意:C++ 在创建变量时,必须给变量一个初始值,否则会报错。 #### 常量 作用:用于记录程序中不可更改的数据。 两种方式: ##### #define 宏常量 ```cpp #define 常量名 常量值 ``` 通常在文件上方定义,表示一个常量。 ##### const 修饰的变量 语法:`const 数据类型 常量名 = 常量值` 通常在变量定义前加关键字 const,修饰该变量为常量,不可修改。 ```cpp #include using namespace std; // 1、宏常量 #define day 7 int main() { cout << "一周里总共有 " << day << " 天" << endl; // day = 8; // 报错,宏常量不可以修改 // 2、const 修饰变量 const int month = 12; cout << "一年里总共有 " << month << " 个月份" << endl; // month = 24; // 报错,常量是不可以修改的 system("pause"); return 0; } ``` #### 关键字 关键字是 C++ 中预先保留的单词(标识符)。 ![[cpp-image1.png]] #### 标识符命名规则 作用:C++ 规定给标识符(变量、常量)命名时,有一套自己的规则。 (1)标识符不能是关键字 (2)标识符只能由字母、数字、下划线组成 (3)第一个字符必须为字母或下划线 (4)标识符中字母区分大小写 给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读。 ### 数据类型 C++ 规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。 #### 整型 作用:整型变量表示的是==整数类型==的数据。 C++ 中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同: ![[cpp-image2.png]] #### 实型(浮点型) 作用:用于表示小数。 浮点型变量分为两种:单精度 float、双精度 double。两者的区别在于表示的有效数字范围不同。 ![[cpp-image3.png]] 使用 `float a = 3.14;` 会进行转换,因为计算机看到 3.14 就认为是 double——实际上这条语句还做了一次把 double 3.14 转换成 float 3.14 的操作。所以在使用 float 的时候可以采用 `float a = 3.14f;`,告诉计算机这是单精度。 ![[cpp-image4.png]] ![[cpp-image5.png]] 使用 cout 输出时默认只会保留六位有效数字。 ![[cpp-image6.png]] #### 字符型 作用:字符型变量用于显示单个字符。 语法:`char ch = 'a';` 注意 1:在显示字符型变量时,用单引号将字符括起来,不要用双引号。 注意 2:单引号内只能有一个字符,不可以是字符串。 C 和 C++ 中字符型变量只占用 1 个字节。字符型变量并不是把字符本身放到内存中存储,而是将对应的 ASCII 编码放入到存储单元。 ASCII 码大致由以下两部分组成: - ASCII 非打印控制字符:ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。 - ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。 tips:按住 alt 再按一个数字,就可以打出它的 ASCII 码对应的字符。 对于一些不能显示出来的 ASCII 字符: ![[cpp-image7.png]] 要用到转义字符。 #### sizeof 关键字 作用:利用 sizeof 关键字可以统计数据类型所占内存大小。 语法:`sizeof(数据类型 / 变量)` ```cpp int a = 3; sizeof(int); sizeof(a); ``` #### 字符串 作用:用于表示一串字符。 两种风格: **C 风格字符串**:`char 变量名[] = "字符串值"` ```cpp char str1[] = "hello world"; cout << str1 << endl; ``` **C++ 风格字符串**:需要加入头文件 `#include `,语法:`string 变量名 = "字符串值"` ```cpp string str = "hello world"; cout << str << endl; ``` 在 "" 里的就是字符串,直接出现在 cout 中也是一样。字符串存放在一个比较高的地址。所谓字符串类型也不过就是把这串字母做一个打包、给它一个名字,以实现再次使用时无需重新打出一整串,而只要用一个 str1 或者 str 就行。 #### bool 类型 作用:布尔数据类型代表真或假的值。 bool 类型只有两个值: - true --- 真(本质是 1) - false --- 假(本质是 0) bool 类型占==1 个字节==大小。 ### 输入输出 作用:用于从键盘获取数据。 关键字:cin。语法:`cin >> 变量` ![[cpp-image8.png]] ```cpp // 整型输入 int a; cout << "请输入整型变量:"; cin >> a; cout << a << endl; // 浮点型输入 double d; cout << "请输入浮点型变量:"; cin >> d; cout << d << endl; // 字符型输入 char ch; cout << "请输入字符型变量:"; cin >> ch; cout << ch << endl; // 字符串型输入 string str; cout << "请输入字符串型变量:"; cin >> str; cout << str << endl; // 布尔类型输入 bool flag; cout << "请输入布尔型变量:"; cin >> flag; cout << flag << endl; ``` ### 运算符 作用:用于执行代码的运算。 | 运算符类型 | 作用 | |---|---| | 算术运算符 | 用于处理四则运算 | | 赋值运算符 | 用于将表达式的值赋给变量 | | 比较运算符 | 用于表达式的比较,并返回一个真值或假值 | | 逻辑运算符 | 用于根据表达式的值返回真值或假值 | #### 算术运算符 作用:用于处理四则运算。 算术运算符包括以下符号: | 运算符 | 术语 | 示例 | 结果 | |---|---|---|---| | + | 正号 | +3 | 3 | | - | 负号 | -3 | -3 | | + | 加 | 10 + 5 | 15 | | - | 减 | 10 - 5 | 5 | | * | 乘 | 10 * 5 | 50 | | / | 除 | 10 / 5 | 2 | | % | 取模(取余) | 10 % 3 | 1 | | ++ | 前置递增 | a=2; b=++a; | a=3; b=3; | | ++ | 后置递增 | a=2; b=a++; | a=3; b=2; | | -- | 前置递减 | a=2; b=--a; | a=1; b=1; | | -- | 后置递减 | a=2; b=a--; | a=1; b=2; | 注意: - 在除法运算中,除数不能为 0 - 只有整型变量可以进行取模运算 - 前置递增先对变量进行 ++,再计算表达式,后置递增相反 #### 赋值运算符 作用:用于将表达式的值赋给变量。 赋值运算符包括以下几个符号: | 运算符 | 术语 | 示例 | 结果 | |---|---|---|---| | = | 赋值 | a=2; b=3; | a=2; b=3; | | += | 加等于 | a=0; a+=2; | a=2; | | -= | 减等于 | a=5; a-=3; | a=2; | | *= | 乘等于 | a=2; a*=2; | a=4; | | /= | 除等于 | a=4; a/=2; | a=2; | | %= | 模等于 | a=3; a%=2; | a=1; | #### 比较运算符 作用:用于表达式的比较,并返回一个真值或假值。 比较运算符有以下符号: | 运算符 | 术语 | 示例 | 结果 | |---|---|---|---| | == | 相等于 | 4 == 3 | 0 | | != | 不等于 | 4 != 3 | 1 | | < | 小于 | 4 < 3 | 0 | | > | 大于 | 4 > 3 | 1 | | <= | 小于等于 | 4 <= 3 | 0 | | >= | 大于等于 | 4 >= 1 | 1 | 注意:C 和 C++ 语言的比较运算中,"真"用数字 "1" 来表示,"假"用数字 "0" 来表示。 #### 逻辑运算符 作用:用于根据表达式的值返回真值或假值。 逻辑运算符有以下符号: | 运算符 | 术语 | 示例 | 结果 | |---|---|---|---| | ! | 非 | !a | 如果 a 为假,则 !a 为真;如果 a 为真,则 !a 为假。 | | && | 与 | a && b | 如果 a 和 b 都为真,则结果为真,否则为假。 | | \|\| | 或 | a \|\| b | 如果 a 和 b 有一个为真,则结果为真,二者都为假时,结果为假。 | 非:真变假,假变真。 与:同真为真,其余为假。 或:同假为假,其余为真。 ### 程序流程结构 C/C++ 支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。 - 顺序结构:程序按顺序执行,不发生跳转 - 选择结构:依据条件是否满足,有选择的执行相应功能 - 循环结构:依据条件是否满足,循环多次执行某段代码 #### 选择结构 ##### if 语句 作用:执行满足条件的语句。 if 语句的三种形式:单行格式 if 语句、多行格式 if 语句、多条件的 if 语句。 ![[cpp-image9.png]] 单行格式 if 语句: ```cpp if (条件) { 条件满足执行的语句; } ``` 多行格式 if 语句: ![[cpp-image10.png]] ```cpp if (条件) { 条件满足执行的语句; } else { 条件不满足执行的语句; } ``` 多条件的 if 语句: ![[cpp-image11.png]] ```cpp if (条件1) { 条件1满足执行的语句; } else if (条件2) { 条件2满足执行的语句; } ... else { 都不满足执行的语句; } ``` 嵌套 if 语句:在 if 语句中,可以嵌套使用 if 语句,达到更精确的条件判断。 ##### 三目运算符 作用:通过三目运算符实现简单的判断。 语法:`表达式1 ? 表达式2 : 表达式3` - 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果 - 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果 返回的是变量,可以继续当成左值被赋值: ```cpp int a = 10; int b = 20; (a > b ? a : b) = 100; ``` 相当于给 b 赋值 100。和 if 语句比较,三目运算符优点是短小整洁,缺点是如果用嵌套,结构不清晰。 ##### switch 语句 作用:执行多条件分支语句。 ```cpp switch(表达式) { case 结果1: 执行语句; break; case 结果2: 执行语句; break; ... default: 执行语句; break; } ``` 注意: - switch 语句中表达式类型只能是整型或者字符型 - case 里如果没有 break,那么程序会一直向下执行 - 与 if 语句比,对于多条件判断时,switch 的结构清晰,执行效率高;缺点是 switch 不可以判断区间 #### 循环结构 ##### while 作用:满足循环条件,执行循环语句。 ```cpp while (循环条件) { 循环语句; } ``` 只要循环条件的结果为真,就执行循环语句。在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环。 ##### do...while 作用:满足循环条件,执行循环语句。 ```cpp do { 循环语句; } while (循环条件); ``` 与 while 的区别在于 do...while 会先执行一次循环语句,再判断循环条件。 ##### for 作用:满足循环条件,执行循环语句。 ```cpp for (起始表达式; 条件表达式; 末尾循环体) { 循环语句; } ``` for 循环中的表达式,要用分号进行分隔。三者都是常用的循环语句,for 循环结构比较清晰,用的较多。 #### 跳转语句 ##### break 语句 作用:用于跳出选择结构、循环结构。 break 使用的时机: - 出现在 switch 条件语句中,作用是终止 case 并跳出 switch - 出现在循环语句中,作用是跳出当前的循环语句 - 出现在嵌套循环中,跳出最近的内层循环语句 ##### continue 语句 作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。 continue 并没有使整个循环终止,而 break 会跳出循环。找奇数: ```cpp for (int i = 0; i < 100; i++) { if (i % 2 == 0) continue; cout << i << endl; } ``` ##### goto 语句 作用:可以无条件跳转语句。 语法:`goto 标记;` 解释:如果标记的名称存在,执行到 goto 语句时,会跳转到标记的位置。在程序中不建议使用 goto 语句,以免造成程序流程混乱。 #### 练习:C++ 猜数 ```cpp #include #include using namespace std; int main() { srand((unsigned int)time(NULL)); int num = rand() % 100 + 1; int val; while (1) { cin >> val; if (val > num) { cout << "大了" << endl; } else if (val < num) { cout << "小了" << endl; } else { cout << "猜对了" << endl; break; } } system("pause"); return 0; } ``` 仍然是用系统时间作为随机数种子,不一样的地方在于头文件变成了 ctime。 #### 敲桌子 输出 1~100 中所有不含 7 的数(7 的倍数或含数字 7 的"敲桌子"): ```cpp #include using namespace std; int main() { for (int i = 1; i <= 100; i++) { if (i % 7 == 0 || i % 10 == 7 || i / 10 == 7) cout << "敲桌子" << endl; else cout << i << endl; } system("pause"); return 0; } ``` #### 九九乘法表 ```cpp #include using namespace std; int main() { for (int i = 1; i < 10; i++) { for (int j = 1; j <= i; j++) cout << j << "*" << i << "=" << j * i << "\t"; cout << endl; } system("pause"); return 0; } ``` `cout << endl;` 相当于 `printf("\n");`。cout 采用的就不再是之前填空的方式了,貌似更直接了当:输出什么、再输出什么。 ### 数组 所谓数组,就是一个集合,里面存放了相同类型的数据元素。 - 特点 1:数组中的每个数据元素都是相同的数据类型 - 特点 2:数组是由连续的内存位置组成的 #### 一维数组 一维数组定义的三种方式: ```cpp 数据类型 数组名[数组长度]; 数据类型 数组名[数组长度] = { 值1, 值2 ... }; 数据类型 数组名[] = { 值1, 值2 ... }; ``` 注意:数组名的命名规范与变量名命名规范一致,不要和变量重名;数组中下标是从 0 开始索引。 数组名的用途: - 统计整个数组在内存中的长度: ```cpp cout << "整个数组所占内存空间为: " << sizeof(arr) << endl; ``` - 获取数组在内存中的首地址: ```cpp cout << "数组首地址为: " << (int)arr << endl; ``` 在 C++ 中无需使用 %d、%p 等表示输出的格式。此外还有一些小技巧: ```cpp cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl; cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl; ``` 通过下标找地址,可以发现每个单元间都隔着一个类型的长度: ```cpp cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl; // 与 arr 的地址一样 cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl; ``` 总结: - 直接打印数组名,可以查看数组所占内存的首地址 - 对数组名进行 sizeof,可以获取整个数组占内存空间的大小 - 数组名是常量,不可以作为左值被赋值(const 指针) ##### 示例 **找最大数**: ```cpp int arr[] = { 20,40,27,55,36 }; int max = arr[0]; for (int i = 0; i + 1 < sizeof(arr) / sizeof(arr[0]); i++) { int x = arr[i] > arr[i + 1] ? arr[i] : arr[i + 1]; // 相邻两个取大 max = x > max ? x : max; } cout << max << endl; ``` **数组逆序**: ```cpp int arr[] = { 1,2,3,4,5 }; int first = 0; int last = sizeof(arr) / sizeof(arr[0]) - 1; int n; for (; first < last; first++, last--) { n = arr[first]; arr[first] = arr[last]; arr[last] = n; } for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) cout << arr[i]; ``` **冒泡排序**: ```cpp int arr[9] = { 4,2,8,0,5,7,1,3,9 }; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++) { for (int j = 0; j < sizeof(arr) / sizeof(arr[0]) - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) cout << arr[i] << endl; ``` 单步运行观察 temp 的交换过程: ![[cpp-image12.png]] #### 二维数组 二维数组就是在一维数组上,多加一个维度。 四种定义方式: ```cpp 数据类型 数组名[行数][列数]; 数据类型 数组名[行数][列数] = { {数据1, 数据2}, {数据3, 数据4} }; 数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 }; 数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 }; ``` 第二种更加直观,提高代码的可读性。在定义二维数组时,如果初始化了数据,可以省略行数。 二维数组数组名的用途:查看二维数组所占内存空间、获取二维数组首地址。 ```cpp cout << "二维数组大小: " << sizeof(arr) << endl; cout << "二维数组一行大小: " << sizeof(arr[0]) << endl; cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl; cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl; cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl; cout << "二维数组首地址:" << arr << endl; cout << "二维数组第一行地址:" << arr[0] << endl; cout << "二维数组第二行地址:" << arr[1] << endl; cout << "二维数组第一个元素地址:" << &arr[0][0] << endl; cout << "二维数组第二个元素地址:" << &arr[0][1] << endl; ``` - 二维数组名就是这个数组的首地址 - 对二维数组名进行 sizeof 时,可以获取整个二维数组占用的内存空间大小 ### 函数 #### 概述 将一段经常使用的代码封装起来,减少重复代码。一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。 主要有 5 个步骤:返回值类型、函数名、参数表列、函数体语句、return 表达式。 ```cpp 返回值类型 函数名(参数列表) { 函数体语句; return 表达式; } ``` - 返回值类型:一个函数可以返回一个值,在函数定义中 - 函数名:给函数起个名称 - 参数列表:使用该函数时,传入的数据 - 函数体语句:花括号内的代码,函数内需要执行的语句 - return 表达式:和返回值类型挂钩,函数执行完后,返回相应的数据 #### 调用 功能:使用定义好的函数。 语法:`函数名(参数)` 函数定义里小括号内称为形参,函数调用时传入的参数称为实参。 做的是值传递。所谓值传递,就是函数调用时实参将数值传入给形参。值传递时,如果形参发生改变,并不会影响实参。 #### 样式 有 4 种: ```cpp // 无参无返 void test01() { // void a = 10; // 无类型不可以创建变量,原因无法分配内存 cout << "this is test01" << endl; } // 有参无返 void test02(int a) { cout << "this is test02" << endl; cout << "a = " << a << endl; } // 无参有返 int test03() { cout << "this is test03 " << endl; return 10; } // 有参有返 int test04(int a, int b) { cout << "this is test04 " << endl; int sum = a + b; return sum; } ``` #### 声明 作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。 函数的声明可以多次,但是函数的定义只能有一次。 ```cpp int max(int a, int b); int max(int, int); ``` #### 分文件编写 让代码结构更加清晰。函数分文件编写一般有 4 个步骤: (1)创建后缀名为 .h 的头文件 (2)创建后缀名为 .cpp 的源文件 (3)在头文件中写函数的声明 (4)在源文件中写函数的定义 swap.h 文件: ```cpp #include using namespace std; // 实现两个数字交换的函数声明 void swap(int a, int b); ``` swap.cpp 文件: ```cpp #include "swap.h" void swap(int a, int b) { int temp = a; a = b; b = temp; cout << "a = " << a << endl; cout << "b = " << b << endl; } ``` main 函数文件: ```cpp #include "swap.h" int main() { int a = 100; int b = 200; swap(a, b); system("pause"); return 0; } ``` ### 指针 #### 基本概念 作用:可以通过指针间接访问内存。 - 内存编号是从 0 开始记录的,一般用十六进制数字表示 - 可以利用指针变量保存地址 #### 定义和使用 定义语法:`数据类型 * 变量名;` ```cpp int a = 10; int* p; p = &a; cout << &a << endl; cout << p << endl; cout << "*p = " << *p << endl; ``` #### 指针变量和普通变量的区别 普通变量存放的是数据,指针变量存放的是地址。 指针变量可以通过 "*" 操作符操作指针变量指向的内存空间,这个过程称为解引用: - 通过 & 符号获取变量的地址 - 通过 * 符号解引用获得变量值 - 利用指针可以记录地址 - 对指针变量解引用,可以操作指针指向的内存 #### 指针所占内存空间 指针也是种数据类型,那么这种数据类型占用多少内存空间? ![[cpp-image13.png]] 所有指针类型在 32 位操作系统下是 4 个字节(X86),在 64 位操作系统下是 8 个字节(X64)。 #### 空指针 指针变量指向内存中编号为 0 的空间。 用途:初始化指针变量(一开始不知道指向哪合适,就初始化等于 NULL)。 注意:空指针指向的内存是不可以访问的。 ```cpp // 指针变量 p 指向内存地址编号为 0 的空间 int* p = NULL; // 访问空指针报错 // 内存编号 0~255 为系统占用内存,不允许用户访问 cout << *p << endl; ``` ![[cpp-image14.png]] #### 野指针 指针变量指向非法的内存空间。 随便给出一个十六进制数,把它强转为地址,用指针变量存放它,理论上是可行的。但这就像是跑到宾馆去开别人的房门,不太道德。为了防止这种不道德的事情发生,编译器会阻止你,告诉你没有权限访问——因为这块内存并不是自己申请来的。 ```cpp // 指针变量 p 指向内存地址编号为 0x1100 的空间 int* p = (int*)0x1100; // 访问野指针报错 cout << *p << endl; ``` 空指针和野指针都不是我们申请的空间,因此不要访问。 #### 指针与 const const 修饰指针有三种情况:const 修饰指针(常量指针)、const 修饰常量(指针常量)、const 既修饰指针又修饰常量。 ##### 常量指针 `const int* p` / `int const* p` const 在 * 号前面(修饰 \*p 不能改变,但 p 能变): - 指针的指向可以修改(可以指向其他地址) - 但不可以通过指针去修改所指向地址里的值 ```cpp const int* p1 = &a; int const* p2 = &b; p1 = &c; // 正确 p2 = &c; // 正确 // *p1 = 100; 报错 // *p2 = 100; 报错 ``` ##### 指针常量 `int* const p` const 在 * 后面,修饰 p(p 放的是地址,意思是地址不能变): - 指针指向不可以改,指针指向的值可以更改 - (只能固定指向某一处,不能指向别处,可以通过 `*p` 去修改该地址里的值) ```cpp int* const p3 = &a; // p3 = &b; // 错误 *p3 = 100; // 正确 ``` ##### const 既修饰指针,又修饰常量 既不可以改指向,又不可以通过 `*p` 去改里面的值。 ```cpp const int* const p3 = &a; // p3 = &b; // 错误 // *p3 = 100; // 错误 ``` 看 const 右侧紧跟着的是什么、它在 \* 的前面还是后面,就知道到底是什么不能改。 ##### constexpr ![[cpp-image15.jpeg]] ![[cpp-image16.jpeg]] ![[cpp-image17.jpeg]] #### 指针与数组 可以利用指针访问数组中元素。 数组其实就可以看做是一个指针常量 `type* const p`——一旦定义就只能指向这么一块连续的内存单元;数组名是首地址,相当于指针的名字。指针之间是可以赋值的,用这个指针赋值给另一个指针,使得两个指针都指向同一块地方。利用这一点就能实现用指针访问数组中的每一个元素: ```cpp int arr[] = { 1,2,3 }; int *p = arr; // 相当于把一个指针的值赋给另一个指针 ``` ```cpp int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; cout << "第一个元素: " << arr[0] << endl; cout << "把数组当指针常量访问第一个元素: " << *arr << endl; cout << "指针访问第一个元素: " << *p << endl; for (int i = 0; i < 10; i++) { cout << arr[i] << endl; } for (int i = 0; i < 10; i++) { cout << *p++ << endl; // 利用指针遍历数组 } ``` 但是不能 arr++,因为它只能指向数组的头。其他方面两者都可以换来换去用,是一样的。 #### 指针和函数 利用指针作函数参数,可以修改实参的值——值传递和地址传递的区别: 原本函数里只是传值,里面的修改对外面没有影响,要做改变得靠返回值。但传地址就不一样了,直接用门牌号让函数直接拿外面的实参进行操作,函数里的操作就是对外面数据本身的操作,因此可以实现修改的功能。 ```cpp // 值传递 void swap1(int a, int b) { int temp = a; a = b; b = temp; } // 地址传递 void swap2(int* p1, int* p2) { int temp = *p1; *p1 = *p2; *p2 = temp; } int main() { int a = 10; int b = 20; swap1(a, b); // 值传递不会改变实参 swap2(&a, &b); // 地址传递会改变实参 cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; } ``` 如果不想修改实参,就用值传递;如果想修改实参,就用地址传递。 #### 指针、数组、函数 ```cpp // 冒泡排序函数 void bubbleSort(int* arr, int len) // int * arr 也可以写为 int arr[] { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } // 打印数组函数 void printArray(int arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << endl; } } int main() { int arr[10] = { 4,3,6,9,1,2,10,8,7,5 }; int len = sizeof(arr) / sizeof(int); bubbleSort(arr, len); printArray(arr, len); system("pause"); return 0; } ``` 当数组名传入到函数作为参数时,被退化为指向首元素的指针。 ### 结构 #### 概念 结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。 #### 定义/使用 ```cpp struct 结构体名 { 结构体成员列表 }; struct student { string name; int age; int score; }; ``` 通过结构体创建变量的方式有三种: (1) 定义结构体时顺便创建变量: ```cpp struct student { string name; int age; int score; }stu; ``` (2) `struct 结构体名 变量名`: ```cpp struct student stu; stu.name = "张三"; stu.age = 18; stu.score = 100; ``` (3) `struct 结构体名 变量名 = { 成员1值, 成员2值... }`: ```cpp struct student stu = { "李四",19,60 }; ``` 输出: ```cpp cout << "姓名:" << stu.name << " 年龄:" << stu.age << " 分数:" << stu.score << endl; ``` 在 C++ 中: - 定义结构体时的关键字是 struct,不可省略 - 创建结构体变量时,关键字 struct 可以省略(C 中不能省) - 结构体变量利用操作符 "." 访问成员 #### 结构体数组 将自定义的结构体放入到数组中方便维护。 语法: ```cpp struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }; ``` ```cpp struct student { string name; // 姓名 int age; // 年龄 int score; // 分数 }; // main 里 struct student arr[3] = { {"张三",18,80 }, {"李四",19,60 }, {"王五",20,70 } }; for (int i = 0; i < 3; i++) cout << "姓名:" << arr[i].name << " 年龄:" << arr[i].age << " 分数:" << arr[i].score << endl; ``` 相较于 C,C++ 里输出要简单一些。不过输出字符串要头文件 ``。 #### 结构体指针 作用:通过指针访问结构体中的成员。 利用操作符 `->` 可以通过结构体指针访问结构体属性。 ```cpp struct student { string name; int age; int score; }; ``` main 中: ```cpp student stu = { "张三",18,100 }; student* p = &stu; p->score = 80; // 指针通过 -> 操作符可以访问成员 cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl; ``` #### 结构体嵌套结构体 结构体中的成员可以是另一个结构体。 ```cpp // 学生结构体定义 struct student { string name; // 姓名 int age; // 年龄 int score; // 分数 }; // 教师结构体定义 struct teacher { int id; // 职工编号 string name; // 教师姓名 int age; // 教师年龄 struct student stu; // 子结构体 学生 }; ``` #### 结构体做函数参数 作用:将结构体作为参数向函数中传递。 传递方式有两种。 值传递: ```cpp void printStudent(student stu) { stu.age = 28; cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl; } // main 中 student stu = { "张三",18,100 }; printStudent(stu); cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl; ``` 地址传递: ```cpp void printStudent2(student* stu) { stu->age = 28; cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl; } printStudent2(&stu); cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl; ``` 同理,一个不被修改、一个被修改。如果不想修改主函数中的数据,用值传递,反之用地址传递。通常情况下是希望改变的,而如果传值就需要进行复制操作,浪费空间和时间,所以不如传指针来得快。 #### 结构体中的 const 用 const 来防止误操作。像写输出函数时,保证不会修改它的值,就能在函数参数表中加上 const。 ```cpp void printStudent(const student* stu) { // stu->age = 100; // 操作失败,因为加了const修饰 cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl; } ``` const 在参数表时,表示它修饰的东西在该函数内不可被修改。 ### 通讯录管理系统 四个版本的通讯录工程(本机路径存档): ```text D:\VSC++代码\通讯录管理系统\通讯录管理系统\通讯录管理系统.cpp D:\VSC++代码\通讯录链表实现\通讯录链表实现\源.cpp D:\VSC++代码\通讯录_链表_mysql\通讯录_链表_mysql\通讯录_链表_mysql.cpp D:\EasyX代码\图形化通讯录\图形化通讯录\源.cpp ``` ### C11 新特性 #### 别名 typedef 和 using。 ![[cpp-image18.jpeg]] 重点:P61。 ![[cpp-image19.jpeg]] 要理解"推出",不能直接代入。由 `typedef char* pstring;` 得 pstring 为 char 的指针。 ![[cpp-image20.png]] 要把它作为一个整体:`const pstring cstr = 0` 是 `char* const cstr`。 ![[cpp-image21.png]] 意义是 cstr 为常量指针,它的指向不能改。如果直接代入去看,成了 `const char* cstr`,那么意义就变成了指向字符常量的指针,意义扭曲了。 so 重点在于:要当做整体,而非代入。 ![[cpp-image22.jpeg]] #### 未知(需推)类型 ##### auto ![[cpp-image23.jpeg]] ![[cpp-image24.jpeg]] ##### decltype ![[cpp-image25.jpeg]] ![[cpp-image26.jpeg]] ![[cpp-image27.jpeg]] ```cpp int i = 42, *p = &i; ``` `decltype(*p)` 的推断结果为 `int&`。注意:解引用得到的是指针所指的对象,并可以对其进行操作,这很符合引用的作用,所以对于得到 `int&` 的结果可以理解。如果反推的话,若得到的结果是个 int,那就是传值而非指针了,操作不相关了,所以不应该是 int。 另一个问题:`decltype((val))` 这种双重括号得到的一定是引用,而 `decltype(val)` 单重括号得到的是不是引用得根据 val 自身决定。 为什么双重括号一定是引用?把括号内的东西当做表达式去理解: ??理解成变量其实也就是对一块地址取名字,是对该地址的引用?所以这个表达式的结果是引用?以及赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型,这个是否也是这样理解?所以其实变量的一切都是引用,引用的某个内存空间,然后对于变量的引用又是一个引用……??? 这一切都和左值相关:左值返回的是引用类型。双重括号 `(variable)` 将变量视为一个表达式,这个表达式的结果类型是 variable 的类型作为引用——因为双重括号强制将 variable 视为一个左值表达式,而左值表达式的 decltype 结果是引用类型,以便可以修改它们的值。 同样适用于"赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型":赋值表达式的结果就是左操作数本身(左值),其引用类型与左值的类型相同——赋值操作需要将右边的值赋给左边的变量,左值可以被修改,因此结果是一个引用以允许对左值进行修改。 所以可以这样: ```cpp int a = 3, b = 4; decltype((a = b)) result = a; // result 是 int& 类型 ``` 对照三种推导: ```cpp int a = 3, b = 4; decltype(a) x = a; // x 的类型是 int(a 是变量名,取声明类型) decltype(a = b) y = a; // y 的类型是 int&(赋值表达式的结果就是左操作数,是左值) decltype((a = b)) z = a; // z 的类型是 int&(加上括号仍是同一个左值) ``` 总之,decltype 的推导结果取决于表达式的左值或右值属性:变量名(不带括号)取声明类型;带括号或运算的表达式按值类别推导,左值得引用、右值得类型本身。 ##### 关于头文件 name.h or cname C 中的头文件在 C++ 中兼容。 ![[cpp-image28.jpeg]] ## 附件 > 📄 C++.docx(原 word 笔记):[[C++.docx|C++.docx]] --- ⬅️ [[01-C++|C++]] 🏠 [[00-编程语言|00-编程语言]] ➡️ [[03-C++ 核心|C++ 核心]]